package cn.wolfcode.service.impl;

import cn.wolfcode.common.domain.UserInfo;
import cn.wolfcode.common.exception.BusinessException;
import cn.wolfcode.common.web.CodeMsg;
import cn.wolfcode.common.web.Result;
import cn.wolfcode.domain.*;
import cn.wolfcode.feign.AlipayFeginService;
import cn.wolfcode.feign.IntegralFeignService;
import cn.wolfcode.mapper.OrderInfoMapper;
import cn.wolfcode.mapper.PayLogMapper;
import cn.wolfcode.mapper.RefundLogMapper;
import cn.wolfcode.mq.MQConstant;
import cn.wolfcode.mq.OrderTimeOutMessage;
import cn.wolfcode.redis.SeckillRedisKey;
import cn.wolfcode.service.IOrderInfoService;
import cn.wolfcode.service.ISeckillProductService;
import cn.wolfcode.util.IdGenerateUtil;
import cn.wolfcode.web.msg.SeckillCodeMsg;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;

/**
 * Created by wolfcode
 */
@Service
@Slf4j
public class OrderInfoSeviceImpl implements IOrderInfoService {
    @Autowired
    private ISeckillProductService seckillProductService;
    @Autowired
    private OrderInfoMapper orderInfoMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private PayLogMapper payLogMapper;
    @Autowired
    private RefundLogMapper refundLogMapper;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private AlipayFeginService alipayFeginService;

    @Autowired
    private IntegralFeignService integralFeignService;

    @Override
    public OrderInfo selectByUserIdAndSeckillId(Long userId, Long seckillId, Integer time) {
        return orderInfoMapper.selectByUserIdAndSeckillId(userId, seckillId, time);
    }

    // @Async // 异步执行
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String doSeckill(UserInfo userInfo, SeckillProductVo vo) {
        // 1. 扣除秒杀商品库存
        seckillProductService.decrStockCount(vo.getId(), vo.getTime());
        // 2. 创建秒杀订单并保存
        OrderInfo orderInfo = this.buildOrderInfo(userInfo, vo);
        orderInfoMapper.insert(orderInfo);
        // 3. 返回订单编号
        return orderInfo.getOrderNo();
    }

    @Override
    public OrderInfo selectByOrderNo(String orderNo, Long phone) {
        OrderInfo orderInfo = orderInfoMapper.find(orderNo);
        if (orderInfo == null) {
            throw new BusinessException(new CodeMsg(500001, "订单不存在"));
        }
        if (!orderInfo.getUserId().equals(phone)) {
            throw new BusinessException(new CodeMsg(500002, "订单不属于当前用户"));
        }
        return orderInfo;
    }

    @Override
    // @Cacheable(key = " 'orders:'  + #orderNo" ,cacheNames = "orderInfo")
    public OrderInfo selectByOrderNoRedis(String orderNo, Long phone) {
        String json = redisTemplate.opsForValue().get("orderInfo::orders:" + orderNo);
        if (StringUtils.isEmpty(json)) {
            return JSON.parseObject(json, OrderInfo.class);
        }
        return selectByOrderNo(orderNo, phone);
    }


    @Override
    public OrderInfo findByOrderNo(String orderNo) {
        return orderInfoMapper.find(orderNo);
    }

    @Override
    public void rollbackOrderSeckill(Long seckillId, Integer time, Long userPhone) {
        log.info("[秒杀失败] 回滚令牌: {},库存:{} ,本地标识{}", seckillId, time, userPhone);
        // 1. 用户下单标识回滚
        redisTemplate.execute(RedisScript.of(new ClassPathResource("scripts/fallbackUserFlag.lua")),
                Arrays.asList(SeckillRedisKey.SECKILL_ORDER_HASH.join(seckillId + ""), userPhone + ""), "2");

        // 2. 回滚令牌(如果当前值<1,结果=1,否则结果=当前值+1)
        redisTemplate.execute(RedisScript.of(new ClassPathResource("scripts/fallbackStockCount.lua")),
                Collections.singletonList(SeckillRedisKey.SECKILL_REAL_COUNT_HASH.join(time + "")), seckillId + "");

        //3.本地标识回滚(设置为false),mq 广播回滚
        rocketMQTemplate.syncSend(MQConstant.CANCEL_SECKILL_OVER_SIGE_TOPIC, seckillId);
    }

    @Override
    public void checkPayTimeOut(OrderTimeOutMessage message) {
        // 1. 查询订单是否已经支付
        // 2. 未支付,修改订单状态为超时取消
        int row = orderInfoMapper.updateCancelStatus(message.getOrderNo(), OrderInfo.STATUS_TIMEOUT);
        if (row > 0) {
            // 3. 回滚库存 mysql
            seckillProductService.incrStockCount(message.getSeckillId());

            // 4. 回滚 redis
            rollbackOrderSeckill(message.getSeckillId(), message.getTime(), message.getUserPhone());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void paySuccess(String outTradeNo, String tradeNo, String totalAmount) {
        // 1.校验订单编号是否存在
        OrderInfo orderInfo = orderInfoMapper.find(outTradeNo);
        if (orderInfo == null) {
            throw new BusinessException(SeckillCodeMsg.REMOTE_DATA_ERROR);
        }
        // 2.校验交易金额是否一致
        if (!orderInfo.getSeckillPrice().toString().equals(totalAmount)) {
            throw new BusinessException(SeckillCodeMsg.REMOTE_DATA_ERROR);
        }
        // 3.校验订单状态
        if (!orderInfo.getStatus().equals(OrderInfo.STATUS_ARREARAGE)) {
            // 只是保证幂等性
            log.warn("[支付回调] 订单状态异常,执行幂等性检查,订单:{}", JSONObject.toJSONString(orderInfo));
            return;
        }
        // 4.修改订单状态
        int row = orderInfoMapper.changePayStatus(orderInfo.getOrderNo(), OrderInfo.STATUS_ACCOUNT_PAID, OrderInfo.PAY_TYPE_ONLINE);
        if (row <= 0) {
            log.warn("[支付回调] 订单状态异常,执行幂等性检查,订单:{}", JSONObject.toJSONString(orderInfo));
            return;
        }
        // 5.记录支付日志
        PayLog payLog = new PayLog();
        payLog.setPayType(OrderInfo.PAY_TYPE_ONLINE);
        payLog.setNotifyTime(LocalDateTime.now().toString());
        payLog.setTradeNo(tradeNo);
        payLog.setOutTradeNo(outTradeNo);
        payLog.setTotalAmount(totalAmount);
        payLogMapper.insert(payLog);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doRefund(String outTradeNo, Long phone) {
        // 1.校验订单编号是否存在
        OrderInfo orderInfo = selectByOrderNo(outTradeNo, phone);
        // 2.校验订单状态为已支付
        if (!orderInfo.getStatus().equals(OrderInfo.STATUS_ACCOUNT_PAID)) {
            throw new BusinessException(SeckillCodeMsg.REFUND_ERROR);
        }

        Result<Boolean> refund = null;
        // 5.根据付款类型执行退款
        if (orderInfo.getPayType() == OrderInfo.PAY_TYPE_ONLINE) {
            // 5.1 远程调用支付宝退款接口
            RefundVo refundVo = new RefundVo(outTradeNo, orderInfo.getSeckillPrice().toString(), "不想要了");
            log.info("[支付宝退款] 订单号:{} ,退款信息:{}", outTradeNo, JSONObject.toJSONString(refundVo));
            refund = alipayFeginService.refund(refundVo);
            if (!refund.getData(true)) {
                throw new BusinessException(SeckillCodeMsg.REFUND_ERROR);
            }
            commitRefund(orderInfo);
            return;
        }
        // 5.1 远程调用积分服务,退还积分
        OperateIntergralVo vo = new OperateIntergralVo();
        vo.setValue(orderInfo.getIntergral());
        vo.setUserId(phone);
        vo.setPk(orderInfo.getOrderNo());
        vo.setInfo("秒杀商品退款");
        // 发送事务消息,通知积分服务退还积分
        rocketMQTemplate.sendMessageInTransaction(
                MQConstant.REFUND_INTEGRAL_TX_GROUP,
                MQConstant.REFUND_INTEGRAL_TOPIC,
                MessageBuilder.withPayload(vo).setHeader("orderNo",orderInfo.getOrderNo()).build(),
                orderInfo.getOrderNo()
        );
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void  commitRefund(String orderNo){
        // 1.校验订单编号是否存在
        OrderInfo orderInfo = orderInfoMapper.find(orderNo);
        // 2.校验订单状态为已支付
        if (!orderInfo.getStatus().equals(OrderInfo.STATUS_ACCOUNT_PAID)) {
            throw new BusinessException(SeckillCodeMsg.REFUND_ERROR);
        }
        commitRefund(orderInfo);
    }


    private void commitRefund(OrderInfo orderInfo) {
        // 3.修改订单状态为已退款,乐观锁
        int row = orderInfoMapper.changeRefundStatus(orderInfo.getOrderNo(), OrderInfo.STATUS_REFUND);
        if (row <= 0) {
            throw new BusinessException(SeckillCodeMsg.REFUND_ERROR);
        }
        // 4. 记录退款日志
        RefundLog refundLog = new RefundLog();
        refundLog.setOutTradeNo(orderInfo.getOrderNo());
        refundLog.setRefundTime(new Date());
        refundLog.setRefundType(orderInfo.getPayType());
        refundLog.setRefundReason("不想要了");
        refundLog.setRefundAmount(orderInfo.getSeckillPrice().toString());
        refundLogMapper.insert(refundLog);

        // 6. 回补库存
        // 6.1. 回滚库存 mysql
        seckillProductService.incrStockCount(orderInfo.getSeckillId());
        // 6.2. 回滚 redis
        rollbackOrderSeckill(orderInfo.getSeckillId(), orderInfo.getSeckillTime(), orderInfo.getUserId());
    }


    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public void integralPay(String orderNo, Long phone) {
        // 1.校验订单编号是否存在
        OrderInfo orderInfo = this.selectByOrderNo(orderNo, phone);
        // 2.校验订单状态为未支付
        if (!orderInfo.getStatus().equals(OrderInfo.STATUS_ARREARAGE)) {
            throw new BusinessException(SeckillCodeMsg.PAT_STATUS_ERROR);
        }
        // 5.修改订单状态为已支付
        int row = orderInfoMapper.changePayStatus(orderNo, OrderInfo.STATUS_ACCOUNT_PAID, OrderInfo.PAY_TYPE_INTERGRAL);
        if (row <= 0) {
            throw new BusinessException(SeckillCodeMsg.INTERGRAL_SERVER_ERROR);
        }
        // 3.调用积分服务,扣除积分
        OperateIntergralVo vo = new OperateIntergralVo();
        vo.setInfo("秒杀商品");
        vo.setUserId(phone);
        vo.setPk(orderInfo.getOrderNo());
        vo.setValue(orderInfo.getIntergral());

        Result<String> result = integralFeignService.integralPay(vo);
        // 4.判断积分扣除是否成功
        String tradeNo = result.getData(true);
        if (StringUtils.isEmpty(tradeNo)) {
            throw new BusinessException(SeckillCodeMsg.INTERGRAL_SERVER_ERROR);
        }
        // 6.记录支付日志
        insertPayLog(orderNo, tradeNo, orderInfo.getIntergral() + "", OrderInfo.PAY_TYPE_INTERGRAL);

    }

    private void insertPayLog(String outTradeNo, String tradeNo, String amount, Integer type) {
        PayLog payLog = new PayLog();
        payLog.setPayType(type);
        payLog.setNotifyTime(LocalDateTime.now().toString());
        payLog.setTradeNo(tradeNo);
        payLog.setOutTradeNo(outTradeNo);
        payLog.setTotalAmount(amount);
        payLogMapper.insert(payLog);
    }


    private OrderInfo buildOrderInfo(UserInfo userInfo, SeckillProductVo vo) {
        Date now = new Date();
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setCreateDate(now);
        orderInfo.setDeliveryAddrId(1L);
        orderInfo.setIntergral(vo.getIntergral());
        orderInfo.setOrderNo(IdGenerateUtil.get().nextId() + "");
        orderInfo.setPayType(OrderInfo.PAY_TYPE_ONLINE);
        orderInfo.setProductCount(1);
        orderInfo.setProductId(vo.getProductId());
        orderInfo.setProductImg(vo.getProductImg());
        orderInfo.setProductName(vo.getProductName());
        orderInfo.setProductPrice(vo.getProductPrice());
        orderInfo.setSeckillDate(now);
        orderInfo.setSeckillId(vo.getId());
        orderInfo.setSeckillPrice(vo.getSeckillPrice());
        orderInfo.setSeckillTime(vo.getTime());
        orderInfo.setStatus(OrderInfo.STATUS_ARREARAGE);
        orderInfo.setUserId(userInfo.getPhone());
        return orderInfo;
    }
}
